package io.gitee.zhangbinhub.admin.log.service

import com.fasterxml.jackson.databind.ObjectMapper
import io.gitee.zhangbinhub.acp.boot.exceptions.WebException
import io.gitee.zhangbinhub.acp.boot.log.LogAdapter
import io.gitee.zhangbinhub.acp.core.common.CalendarTools
import io.gitee.zhangbinhub.acp.core.common.CommonTools
import io.gitee.zhangbinhub.admin.common.vo.CustomerQueryPageVo
import io.gitee.zhangbinhub.admin.log.base.BaseLogEntity
import io.gitee.zhangbinhub.admin.log.base.BaseRepository
import io.gitee.zhangbinhub.admin.log.base.BaseService
import io.gitee.zhangbinhub.admin.log.entity.LoginLog
import io.gitee.zhangbinhub.admin.log.entity.OperateLog
import io.gitee.zhangbinhub.admin.log.entity.RouteLog
import io.gitee.zhangbinhub.admin.log.po.LogQueryPo
import io.gitee.zhangbinhub.admin.log.repo.*
import io.gitee.zhangbinhub.admin.log.vo.LoginLogVo
import io.gitee.zhangbinhub.admin.resource.server.feign.CommonOauthServer
import io.gitee.zhangbinhub.admin.resource.server.tools.TokenUserTools
import io.gitee.zhangbinhub.admin.resource.server.vo.ApplicationVo
import io.gitee.zhangbinhub.admin.resource.server.vo.TokenUserInfoVo
import jakarta.persistence.criteria.Predicate
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

/**
 * @since JDK 11
 */
@Service
@Transactional(readOnly = true)
class LogService @Autowired
constructor(
    private val logAdapter: LogAdapter,
    private val objectMapper: ObjectMapper,
    private val tokenUserTools: TokenUserTools,
    private val routeLogRepository: RouteLogRepository,
    private val routeLogHistoryRepository: RouteLogHistoryRepository,
    private val operateLogRepository: OperateLogRepository,
    private val operateLogHistoryRepository: OperateLogHistoryRepository,
    private val loginLogRepository: LoginLogRepository,
    private val loginLogHistoryRepository: LoginLogHistoryRepository,
    private val commonOauthServer: CommonOauthServer
) : BaseService() {

    /**
     * 路由消息转为对应的实体对象
     */
    @Throws(WebException::class)
    private fun <T> messageToEntity(message: String, cls: Class<T>): T {
        return objectMapper.readValue(message, cls) ?: throw WebException("日志消息转换失败")
    }

    private fun getTokenInfo(token: String): TokenUserInfoVo = tokenUserTools.getUserInfoFromToken(token)

    private fun getAppInfo(token: String): ApplicationVo = commonOauthServer.appInfo(token)

    @Transactional
    fun doRouteLog(message: String) {
        val routeLog = messageToEntity(message, RouteLog::class.java)
        if (routeLog.token != null) {
            try {
                getAppInfo(routeLog.token!!).let { app ->
                    routeLog.clientId = app.id
                    routeLog.clientName = app.appName
                    routeLog.identify = app.identify
                    if (!CommonTools.isNullStr(app.id)) {
                        getTokenInfo(routeLog.token!!).let {
                            routeLog.userId = it.id.toString()
                            routeLog.loginNo = it.loginNo.toString()
                            routeLog.userName = it.name.toString()
                        }
                    }
                }
            } catch (e: Exception) {
                logAdapter.error(e.message, e)
            }
        }
        routeLogRepository.save(routeLog)
    }

    @Transactional
    fun doOperateLog(message: String) {
        try {
            val operateLog = messageToEntity(message, OperateLog::class.java)
            getAppInfo(operateLog.token!!).let { app ->
                operateLog.clientId = app.id
                operateLog.clientName = app.appName
                operateLog.identify = app.identify
                if (!CommonTools.isNullStr(app.id)) {
                    getTokenInfo(operateLog.token!!).let {
                        operateLog.userId = it.id.toString()
                        operateLog.loginNo = it.loginNo.toString()
                        operateLog.userName = it.name.toString()
                    }
                    operateLogRepository.save(operateLog)
                }
            }
        } catch (e: Exception) {
            logAdapter.error(e.message, e)
            throw WebException("操作日志记录失败")
        }
    }

    @Transactional
    suspend fun doLoginLog(message: String) {
        try {
            val loginLog = messageToEntity(message, LoginLog::class.java)
            getAppInfo(loginLog.token!!).let { app ->
                if (!CommonTools.isNullStr(app.id)) {
                    loginLog.clientId = app.id
                    loginLog.clientName = app.appName
                    loginLog.identify = app.identify
                    getTokenInfo(loginLog.token!!).let {
                        loginLog.userId = it.id.toString()
                        loginLog.loginNo = it.loginNo.toString()
                        loginLog.userName = it.name.toString()
                        loginLog.loginDate = CalendarTools.getDateTimeString(
                            CalendarTools.getCalendar(loginLog.requestTime),
                            CalendarTools.DATE_FORMAT
                        )
                        loginLogRepository.save(loginLog)
                    }
                }
            }
        } catch (e: Exception) {
            logAdapter.error(e.message, e)
            throw WebException("登录日志记录失败")
        }
    }

    fun loginStatistics(beginTime: Long): List<LoginLogVo> =
        loginLogHistoryRepository.loginStatistics(beginTime).let {
            it.addAll(loginLogRepository.loginStatistics())
            it.map { item ->
                LoginLogVo(item[0].toString(), item[1].toString(), item[2].toString(), item[3].toString().toLong())
            }
        }

    private fun doPageQuery(
        baseRepository: BaseRepository<out BaseLogEntity, String>,
        logQueryPo: LogQueryPo
    ): CustomerQueryPageVo<out BaseLogEntity> =
        baseRepository.findAll({ root, _, criteriaBuilder ->
            val predicateList: MutableList<Predicate> = mutableListOf()
            if (!CommonTools.isNullStr(logQueryPo.remoteIp)) {
                predicateList.add(
                    criteriaBuilder.like(
                        root.get<Any>("remoteIp").`as`(String::class.java),
                        "%" + logQueryPo.remoteIp + "%"
                    )
                )
            }
            if (!CommonTools.isNullStr(logQueryPo.gatewayIp)) {
                predicateList.add(
                    criteriaBuilder.like(
                        root.get<Any>("gatewayIp").`as`(String::class.java),
                        "%" + logQueryPo.gatewayIp + "%"
                    )
                )
            }
            if (!CommonTools.isNullStr(logQueryPo.path)) {
                predicateList.add(
                    criteriaBuilder.like(
                        root.get<Any>("path").`as`(String::class.java),
                        "%" + logQueryPo.path + "%"
                    )
                )
            }
            if (!CommonTools.isNullStr(logQueryPo.serverId)) {
                predicateList.add(
                    criteriaBuilder.like(
                        root.get<Any>("serverId").`as`(String::class.java),
                        "%" + logQueryPo.serverId + "%"
                    )
                )
            }
            if (!CommonTools.isNullStr(logQueryPo.clientName)) {
                predicateList.add(
                    criteriaBuilder.like(
                        root.get<Any>("clientName").`as`(String::class.java),
                        "%" + logQueryPo.clientName + "%"
                    )
                )
            }
            if (!CommonTools.isNullStr(logQueryPo.userName)) {
                predicateList.add(
                    criteriaBuilder.like(
                        root.get<Any>("userName").`as`(String::class.java),
                        "%" + logQueryPo.userName + "%"
                    )
                )
            }
            if (logQueryPo.startTime != null) {
                predicateList.add(
                    criteriaBuilder.ge(
                        root.get<Any>("requestTime").`as`(Long::class.java),
                        logQueryPo.startTime
                    )
                )
            }
            if (logQueryPo.endTime != null) {
                predicateList.add(
                    criteriaBuilder.le(
                        root.get<Any>("requestTime").`as`(Long::class.java),
                        logQueryPo.endTime
                    )
                )
            }
            if (logQueryPo.responseStatus != null) {
                predicateList.add(
                    criteriaBuilder.equal(
                        root.get<Any>("responseStatus").`as`(Long::class.java),
                        logQueryPo.responseStatus
                    )
                )
            }
            criteriaBuilder.and(*predicateList.toTypedArray())
        }, buildPageRequest(logQueryPo.queryParam!!)).let { page ->
            CustomerQueryPageVo(
                currPage = logQueryPo.queryParam!!.currPage!!,
                pageSize = logQueryPo.queryParam!!.pageSize!!,
                totalElements = page.totalElements,
                content = page.content
            )
        }

    fun doQueryRouteLog(logQueryPo: LogQueryPo): CustomerQueryPageVo<out BaseLogEntity> =
        logQueryPo.let {
            if (it.history) {
                routeLogHistoryRepository
            } else {
                routeLogRepository
            }
        }.let {
            doPageQuery(it, logQueryPo)
        }

    fun doQueryOperateLog(logQueryPo: LogQueryPo): CustomerQueryPageVo<out BaseLogEntity> =
        logQueryPo.let {
            if (it.history) {
                operateLogHistoryRepository
            } else {
                operateLogRepository
            }
        }.let {
            doPageQuery(it, logQueryPo)
        }

    fun doQueryLoginLog(logQueryPo: LogQueryPo): CustomerQueryPageVo<out BaseLogEntity> =
        logQueryPo.let {
            if (it.history) {
                loginLogHistoryRepository
            } else {
                loginLogRepository
            }
        }.let {
            doPageQuery(it, logQueryPo)
        }

}
